﻿// Copyright 2009 Mike Geise
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Falcon.Core.Services
{
	using System;
	using Falcon.Core.Collections;
	using Falcon.Core.Domain;
	using Falcon.Core.Domain.Enums;
	using Falcon.Core.Domain.Repositories;
	using Falcon.Core.Domain.Services;
	using Falcon.Core.Domain.Values;
	using Falcon.Core.Extensions;

	public class UserBadgeService : IUserBadgeService
	{
		/// <summary>
		/// Initializes a new instance of the <see cref="UserBadgeService"/> class.
		/// </summary>
		/// <param name="fileService">The file service.</param>
		/// <param name="imageService">The image service.</param>
		/// <param name="userService">The user service.</param>
		/// <param name="userBadgeRepository">The user badge repository.</param>
		public UserBadgeService(
			IFileService fileService,
			IImageService imageService,
			IUserService userService,
			IUserBadgeRepository userBadgeRepository)
		{
			this.FileService = fileService;
			this.ImageService = imageService;
			this.UserService = userService;
			this.UserBadgeRepository = userBadgeRepository;
		}

		/// <summary>
		/// Gets or sets the file service.
		/// </summary>
		/// <value>The file service.</value>
		public IFileService FileService
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets or sets the image service.
		/// </summary>
		/// <value>The image service.</value>
		public IImageService ImageService
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets or sets the user service.
		/// </summary>
		/// <value>The user service.</value>
		public IUserService UserService
		{
			get; 
			private set;
		}

		/// <summary>
		/// Gets or sets the user badge repository.
		/// </summary>
		/// <value>The user badge repository.</value>
		public IUserBadgeRepository UserBadgeRepository
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets the user badge by the specified specification.
		/// </summary>
		/// <param name="specification">The <see cref="UserBadgeSpecification"/>.</param>
		/// <returns></returns>
		public UserBadge GetBy(UserBadgeSpecification specification)
		{
			return this.UserBadgeRepository.GetBy(specification);
		}

		/// <summary>
		/// Gets the user badge by the specified id.
		/// </summary>
		/// <param name="id">The id.</param>
		/// <returns></returns>
		public UserBadge GetById(int id)
		{
			return this.UserBadgeRepository.GetById(id);
		}

		/// <summary>
		/// Gets all the users badges by the specified specification.
		/// </summary>
		/// <param name="specification">The <see cref="UserBadgeSpecification"/>.</param>
		/// <returns></returns>
		public IPagedList<UserBadge, UserBadgeSpecification> GetPaged(UserBadgeSpecification specification)
		{
			return this.UserBadgeRepository.GetPaged(specification);
		}

		/// <summary>
		/// Gets all the users badges with the user by the specified specification.
		/// </summary>
		/// <param name="specification">The <see cref="UserBadgeSpecification"/>.</param>
		/// <returns></returns>
		public IPagedList<UserBadge, UserBadgeSpecification> GetUsers(UserBadgeSpecification specification)
		{
			return this.UserBadgeRepository.GetUsers(specification);
		}

		/// <summary>
		/// Gets all the users badges grouped by the specified specification.
		/// </summary>
		/// <param name="specification">The <see cref="UserBadgeSpecification"/>.</param>
		/// <returns></returns>
		public IPagedList<UserBadge, UserBadgeSpecification> GetGrouped(UserBadgeSpecification specification)
		{
			return this.UserBadgeRepository.GetGrouped(specification);
		}

		/// <summary>
		/// Creates the user badge.
		/// </summary>
		/// <returns></returns>
		public UserBadge Create()
		{
			return new UserBadge
			{
				Created = DateTime.Now
			};
		}

		/// <summary>
		/// Inserts the specified badge.
		/// </summary>
		/// <param name="badge">The badge.</param>
		public void Insert(UserBadge badge)
		{
			badge.Validate();

			badge.Slug = badge.Title.Slug();
			badge.BodyParsed = badge.Body.BBCodeToHtml();

			this.UserBadgeRepository.Insert(badge);
			this.UpdateUser(badge.UserId, badge.Reputation);
		}

		/// <summary>
		/// Inserts the specified badge.
		/// </summary>
		/// <param name="badge">The badge.</param>
		/// <param name="value">The value.</param>
		public void Insert(UserBadge badge, UserBadgeCreate value)
		{
			value.Validate();

			var user = this.UserService.GetBy(new UserSpecification
			{
				Name = value.Name
			});

			if (user == null)
			{
				return;
			}

			badge.UserId = user.Id;

			if (value.Existing)
			{
				var existing = this.GetById(value.Id);

				badge.Title = existing.Title;
				badge.Body = existing.Body;
				badge.Reputation = existing.Reputation;
				badge.Scope = existing.Scope;
			}
			else
			{
				badge.Title = value.Title;
				badge.Body = value.Body;
				badge.Reputation = value.Reputation;
				badge.Scope = value.Scope;

				if (value.Source != null && value.Source.ContentLength > 0)
				{
					var image = this.FileService.Save(value.Source, Settings.Users.BadgeIconPath);

					badge.FileName = image.FileName;
					badge.FilePath = image.FilePath;
					badge.FileType = image.FileType;
				}
			}

			this.Insert(badge);
		}

		/// <summary>
		/// Updates the specified badge.
		/// </summary>
		/// <param name="badge">The badge.</param>
		public void Update(UserBadge badge)
		{
			badge.Validate();

			badge.Slug = badge.Title.Slug();
			badge.BodyParsed = badge.Body.BBCodeToHtml();

			this.UserBadgeRepository.Update(badge);
		}

		/// <summary>
		/// Updates the specified badge.
		/// </summary>
		/// <param name="badge">The badge.</param>
		/// <param name="value">The value.</param>
		public void Update(UserBadge badge, UserBadgeUpdate value)
		{
			value.Validate();

			var user = this.UserService.GetBy(new UserSpecification
			{
				Name = value.Name
			});

			if (user == null)
			{
				return;
			}

			badge.UserId = user.Id;
			badge.Title = value.Title;
			badge.Body = value.Body;
			badge.Reputation = value.Reputation;
			badge.Scope = value.Scope;

			if (value.Source != null && value.Source.ContentLength > 0)
			{
				this.FileService.Delete(badge.FilePath);

				var image = this.FileService.Save(value.Source, Settings.Users.BadgeIconPath);

				badge.FileName = image.FileName;
				badge.FilePath = image.FilePath;
				badge.FileType = image.FileType;
			}

			this.Update(badge);

			if (value.Change == UserBadgeChange.Single)
			{
				return;
			}

			this.UpdateUser(badge.UserId, badge.Reputation, value.ReputationBefore);

			var badges = this.GetPaged(new UserBadgeSpecification
			{
				Title = value.TitleBefore
			});

			foreach (var existing in badges)
			{
				if (existing.Id.Equals(badge.Id))
				{
					continue;
				}

				existing.Title = badge.Title;
				existing.Body = badge.Body;
				existing.Reputation = badge.Reputation;
				existing.FileName = badge.FileName;
				existing.FilePath = badge.FilePath;
				existing.FileType = badge.FileType;
				existing.Scope = badge.Scope;

				this.Update(existing);
				this.UpdateUser(existing.UserId, existing.Reputation, value.ReputationBefore);
			}
		}

		/// <summary>
		/// Deletes the specified badge.
		/// </summary>
		/// <param name="badge">The badge.</param>
		public void Delete(UserBadge badge)
		{
			var user = this.UserService.GetById(badge.UserId);

			user.UpdateReputation(badge.Reputation, false);

			this.UserService.Update(user);
		}

		/// <summary>
		/// Updates the user.
		/// </summary>
		/// <param name="userId">The user id.</param>
		/// <param name="reputation">The reputation.</param>
		private void UpdateUser(int userId, int reputation)
		{
			var user = this.UserService.GetById(userId);

			user.UpdateReputation(reputation, true);

			this.UserService.Update(user);
		}

		/// <summary>
		/// Updates the user.
		/// </summary>
		/// <param name="userId">The user id.</param>
		/// <param name="reputation">The reputation.</param>
		/// <param name="reputationB">The reputation B.</param>
		private void UpdateUser(int userId, int reputation, int reputationB)
		{
			var difference = Math.Abs(reputation - reputationB);

			if (difference == 0)
			{
				return;
			}

			var user = this.UserService.GetById(userId);

			if (reputation > reputationB)
			{
				user.UpdateReputation(difference, true);
			}
			else
			{
				user.UpdateReputation(difference, false);
			}

			this.UserService.Update(user);
		}
	}
}